Solana Pay
Özet
- Solana Pay, farklı Solana uygulamaları ve cüzdanları arasında standartlaştırılmış işlem talepleri sağlamak için URLs'ler içinde Solana işlem taleplerini kodlamak için bir spesifikasyondur.
- Kısmi imzalama, işlemlerin ağa sunulmadan önce birden fazla imza gerektiren işlemlerin oluşturulmasına olanak tanır.
- İşlem kısıtlaması, belirli koşullara veya işlemde belirli verilerin varlığına dayanarak bazı işlemlerin işlenip işlenmeyeceğini belirleyen kuralların uygulanmasını içerir.
Ders
Solana topluluğu sürekli olarak ağın işlevselliğini geliştiriyor ve genişletiyor. Fakat bu her zaman tamamen yeni teknolojiler geliştirmek anlamına gelmiyor. Bazen, ağın mevcut özelliklerini yeni ve yenilikçi şekillerde kullanmak anlamına geliyor.
Solana Pay, ağa yeni işlevsellik eklemek yerine mevcut özellikleri benzersiz bir şekilde kullanarak tüccarların ve uygulamaların işlem talepleri talep etmelerine ve belirli işlem türleri için kısıtlama mekanizmaları oluşturmasına olanak tanır.
Bu derste, Solana Pay'i kullanarak transfer ve işlem talepleri oluşturmayı, bu talepleri QR kodu olarak kodlamayı, işlemleri kısmi olarak imzalamayı ve seçtiğiniz koşullara göre işlemleri kısıtlamayı öğreneceksiniz. Bununla yetinmeyip, mevcut özellikleri yeni ve yenilikçi yollarla kullanmanın bir örneği olarak göreceksiniz ve bunu kendi benzersiz istemci tarafı ağ etkileşimleriniz için bir sıçrama tahtası olarak kullanmayı umuyoruz.
Solana Pay
Solana Pay spesifikasyonu, kullanıcıların URL'leri aracılığıyla ödemeler talep etmesine ve işlemleri başlatmasına olanak tanıyan standartlar grubudur.
Talepler URL'leri solana:
ile başlar, böylece platformlar bağlantıyı uygun uygulamaya yönlendirebilir. Örneğin, mobilde solana:
ile başlayan bir URL, Solana Pay spesifikasyonunu destekleyen cüzdan uygulamalarına yönlendirilir. Buradan sonra cüzdan, URL'nin geri kalanını uygun şekilde işlemek için kullanabilir.
Solana Pay spesifikasyonu tarafından tanımlanan iki tür talep vardır:
- Transfer Talebi: basit SOL veya SPL Token transferleri için kullanılır.
- İşlem Talebi: herhangi bir türde Solana işlemi talep etmek için kullanılır.
Transfer talepleri
Transfer talep spesifikasyonu, SOL veya SPL token transferi için etkileşimsiz bir talep tarif eder. Transfer talep URL'leri aşağıdaki formatta alınır: solana:?
.
alıcı
değeri zorunludur ve bir transfer talep edilecek hesabın base58 kodlu genel anahtarı olmalıdır. Ek olarak, aşağıdaki isteğe bağlı sorgu parametreleri desteklenmektedir:
miktar
- transfer edilecek token miktarını belirten pozitif bir tam sayı veya ondalık değerspl-token
- SPL token transferi yapılıyorsa, bunun için bir SPL Token mint hesabının base58 kodlu genel anahtarıreferans
- istemci tarafından onchain işlem tanımlamak için kullanılabilecek base58 kodlu 32 byte dizileri olarak isteğe bağlı referans değerleri. İstemcinin bir işlemin imzasına sahip olmayacağından kullanılabilir.etiket
- transfer talebinin kaynağını tanımlayan URL kodlu UTF-8 dizgesimesaj
- transfer talebinin doğasını tanımlayan URL kodlu UTF-8 dizgesimemo
- ödeme işleminin SPL memo talimatında dahil edilmesi gereken URL kodlu UTF-8 dizgesi
Bir örnek olarak, 1 SOL için bir transfer talebini tanımlayan bir URL şöyle olabilir:
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?miktar=1&etiket=Michael&mesaj=Thanks%20for%20all%20the%20fish&memo=OrderId12345
Ve işte 0.1 USDC için bir transfer talebini tanımlayan bir URL:
solana:mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN?miktar=0.01&spl-token=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
İşlem talepleri
Solana Pay işlem talebi, destekleyen bir cüzdan tarafından tüketilebilen bir URL olan transfer taleplerine benzer. Ancak bu talep etkileşimli ve formatı daha açık uçludur:
solana:<bağlantı>
bağlantı
değeri, tüketen cüzdanın HTTP isteği yapabileceği bir URL olmalıdır. Bir işlemin gerekli tüm bilgilerini içermek yerine, bir işlem talebi bu URL'yi kullanarak kullanıcıya sunulması gereken işlemi almak için kullanır.
Bir cüzdan, işlem Talep URL'sini aldığında dört şey olur:
- Cüzdan, kullanıcıya gösterilecek bir etiket ve simge resmini almak için sağlanan
bağlantı
URL'sine GET isteği gönderir. - Cüzdan, ardından nihai kullanıcının genel anahtarını içeren bir POST isteği gönderir.
- Nihai kullanıcının genel anahtarı (ve
bağlantı
içinde sağlanan ek bilgiler kullanılarak), uygulama daha sonra işlemi oluşturur ve base64 kodlu serialize edilmiş işlemi geri gönderir. - Cüzdan, işlemi çözer ve ayrıştırır; ardından kullanıcının işlemi imzalamasına ve göndermesine izin verir.
İşlem taleplerinin transfer taleplerinden daha karmaşık olması nedeniyle, dersin geri kalan kısmı işlem talepleri oluşturmaya odaklanacaktır.
İşlem talebi oluşturma
API uç noktasını tanımlayın
Geliştirici olarak, işlem talebi akışının çalışmasını sağlamak için yapmanız gereken temel şey, işlem talebinde dahil etmeyi planladığınız URL’de bir REST API uç noktası kurmaktır. Bu derste, uç noktalarımız için Next.js API Routes kullanacağız, ancak en rahat olduğunuz yığın ve araçları kullanmakta özgürsünüz.
Next.js'de bunu, pages/api
klasörüne bir dosya ekleyerek ve isteği ve yanıtı işleyen bir fonksiyonu dışa aktararak yaparsınız.
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
request: NextApiRequest,
response: NextApiResponse,
) {
// İsteği işleyin
}
GET isteğini işleyin
Cüzdanınız, işlem talep URL'sini tüketirken ilk olarak bu uç noktaya GET isteği gönderir. Uç noktanızın, iki alan içeren bir JSON nesnesi döndürmesi gerekir:
etiket
- işlem talebinin kaynağını tanımlayan bir dizgeikon
- kullanıcıya gösterilebilecek bir resmin URL'si
Önceki boş uç noktanızdan çıkarsak, bu şöyle görünebilir:
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
request: NextApiRequest,
response: NextApiResponse,
) {
if (request.method === "GET") {
return get(response);
}
return response.status(405).json({ hata: "Yöntem izin verilmedi" });
}
function get(response: NextApiResponse) {
response.status(200).json({
etiket: "Mağaza Adı",
ikon: "https://solana.com/src/img/branding/solanaLogoMark.svg",
});
}
Cüzdan API uç noktasına GET isteği yaptığında, get
fonksiyonu çağrılır ve etiket
ve ikon
içeren bir JSON nesnesi ile 200 durum kodu ile bir yanıt döner.
POST isteğini işleyin ve işlemi oluşturun
GET isteğinden sonra, cüzdan aynı URL'ye bir POST isteği yapar. Uç noktanız, POST isteğinin body
bölümünde, istek yapan cüzdan tarafından sağlanan bir JSON nesnesinde hesap
alanı olmasını beklemelidir. hesap
değeri, nihai kullanıcının genel anahtarını temsil eden bir dizge olacaktır.
Bu bilgi ile ve ek parametreler sağlandığında, işlemi oluşturabilir ve imzalanması için cüzdana dönebilirsiniz:
- Solana ağına bağlanarak en son
blockhash
'i almak. - Yeni bir işlem oluşturmak için
blockhash
'i kullanmak. - İşleme talimatlar eklemek.
- İşlemi serialize ederek bir
PostResponse
nesnesi içinde kullanıcıya bir mesaj ile döndürmek.
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
request: NextApiRequest,
response: NextApiResponse,
) {
if (request.method === "GET") {
return get(response);
}
if (request.method === "POST") {
return post(request, response);
}
return response.status(405).json({ hata: "Yöntem izin verilmedi" });
}
function get(response: NextApiResponse) {
response.status(200).json({
etiket: "Mağaza Adı",
ikon: "https://solana.com/src/img/branding/solanaLogoMark.svg",
});
}
async function post(request: NextApiRequest, response: NextApiResponse) {
const { hesap, referans } = request.body;
const connection = new Connection(clusterApiUrl("devnet"));
const { blockhash } = await connection.getLatestBlockhash();
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: hesap,
});
const instruction = SystemProgram.transfer({
fromPubkey: new PublicKey(hesap),
toPubkey: Keypair.generate().publicKey,
lamports: 0.001 * LAMPORTS_PER_SOL,
});
instruction.keys.push({
pubkey: referans,
isSigner: false,
isWritable: false,
});
transaction.add(instruction);
const serializedTransaction = transaction.serialize({
requireAllSignatures: false,
});
const base64 = serializedTransaction.toString("base64");
const message = "0.001 SOL için basit transfer";
response.status(200).json({
işlem: base64,
mesaj,
});
}
Burada alışılmadık bir şey yok. Bu, standart bir istemci tarafı uygulamasında kullanacağınız aynı işlem yapısını olmaktadır. Tek fark, ağa imzalamak ve göndermek yerine işlemi bir base64 kodlu dize olarak HTTP yanıtında geri göndermektir. İsteği yapan cüzdan, ardından işlemi kullanıcıya imzalatmak için gösterebilir.
İşlemi Onaylama
Önceki örnekte bir referans
sağlandığını varsaydığınızı fark etmişsinizdir. Bu, istekte bulunan cüzdan tarafından sağlanan bir değer değildir, ancak işlem talebinizin başlangıç URL'sinin bu sorgu parametresini içermesini sağlamak için yararlıdır.
Uygulamanız, ağa bir işlem gönderen kişi olmadığından, kodunuz bir işlem imzasına erişemez. Bu, uygulamanızın ağa bir işlemi bulabilmesi ve durumunu görmesi için genellikle böyle olur.
Bunu aşmak için, her işlem talebi için bir sorgu parametresi olarak bir referans
değeri ekleyebilirsiniz. Bu değer, işleme dahil edilebilecek bir non-signer anahtarı olarak, base58 kodlu 32 byte dizisi olmalıdır. Bu, uygulamanızın getSignaturesForAddress
RPC yöntemini kullanarak işlemi bulmasına olanak tanır. Uygulamanız, bir işlemin durumuna göre kullanıcı arayüzünü uyarlayabilir.
@solana/pay
kütüphanesini kullanıyorsanız, doğrudan getSignaturesForAddress
kullanmak yerine findReference
yardımcı işlevini kullanabilirsiniz.
Kısıtlı işlemler
Solana Pay'in, mevcut işlevselliklerini kullanarak ağ üzerinde yeni şeyler yapabilme örneği olarak bahsettiğimiz konuyla ilgili olarak, belirli koşullar sağlandığında yalnızca belirli işlemlerin mevcut olmasını sağlamak da buna küçük bir örnektir.
İşlemi oluşturan uç noktayı kontrol ettiğiniz için, bir işlem oluşturulmadan önce hangi kriterlerin sağlanması gerektiğini belirleyebilirsiniz. Örneğin, POST isteğinde sağlanan hesap
alanını kullanarak nihai kullanıcının belirli bir koleksiyondan bir NFT tutup tutmadığını veya bu genel anahtarın bu belirli işlemi gerçekleştirebilecek önceden belirlenmiş bir hesaplar listesinde olup olmadığını kontrol edebilirsiniz.
// belirtilen cüzdan tarafından sahip olunan nfncç'lerin dizisini al
const nfts = await metaplex.nfts().findAllByOwner({ owner: hesap }).run();
// nfts dizisi üzerinde yineleme
for (let i = 0; i < nfts.length; i++) {
// mevcut nft'nin koleksiyon alanının istenen değeriyle olup olmadığını kontrol et
if (nfts[i].collection?.address.toString() == koleksiyon.toString()) {
// işlemi oluştur
} else {
// bir hata döndür
}
}
Kısmi İmzalama
Belirli kısıtlamalara sahip işlemleri isterseniz, bu işlevselliğin de zincir üzerinde uygulanması gerekecektir. Solana Pay uç noktanızdan bir hata döndürmek, nihai kullanıcıların işlemi gerçekleştirmesini zorlaştırır, ancak yine de bunu manuel olarak oluşturabilirler.
Bu, çağrılan işlem (ler) in, yalnızca sizin uygulamanızın sağlayabileceği bir tür "admin" imzasını gerektireceği anlamına gelir. Ancak bunu yaparken, önceki örneklerimizin çalışmamasına neden olursunuz. İşlem, nihai kullanıcının imzası için istek yapan cüzdana gönderilir, ancak sunulan işlem, admin imzası olmadan başarısız olacaktır.
Neyse ki, Solana kısmi imzalamayla imza bileşenliğini mümkün kılar.
Kısmi olarak çoklu imzalı bir işlemi imzalamak, imzacılara işlemin ağa yayınlanmadan önce imzalarını ekleyebilme olanağı sağlar. Bu, aşağıdakiler de dahil olmak üzere birkaç durumda yararlı olabilir:
- Tüccar ve bir alıcı gibi birden fazla tarafın imzasını gerektiren işlemleri onaylama.
- Hem bir kullanıcı hem de bir yöneticinin imzalarını gerektiren özel programları etkinleştirme. Bu, program talimatlarına erişimi sınırlama ve yalnızca yetkili tarafların bunları çalıştırmasını sağlama konusunda yardımcı olabilir.
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
const transaction = new Transaction({
feePayer: hesap,
blockhash,
lastValidBlockHeight,
})
...
transaction.partialSign(adminKeypair)
partialSign
fonksiyonu, herhangi bir önceki imzayı geçersiz kılmadan bir işleme imza eklemek için kullanılır. Birden fazla imzaya sahip bir işlem oluşturuyorsanız, bir işlemin feePayer
değerini belirtmezseniz, ilk imzalayıcı işlem için ücret ödeyen kişi olarak kullanılacağını hatırlamak önemlidir. Herhangi bir karışıklığı veya beklenmedik davranışları önlemek için, gerektiğinde ücret ödeyeni açıkça ayarladığınızdan emin olun.
Sadece nihai kullanıcının belirli bir NFT'ye sahip olduğu durumda bir işlem talebinin geçmesine izin verme örneğimizde, işlemi bir base64 kodlu dize olarak kodlamadan önce, işlemi ekleyerek yanıt vermeniz yeterli olacaktır.
Solana Pay QR kodları
Solana Pay'in dikkate değer özelliklerinden biri, QR kodlarıyla kolay entegrasyondur. Transfer ve işlem talepleri basit URL'ler olduğundan, bunları uygulamanızda veya başka bir yerde kullanılabilir QR kodlarına gömebilirsiniz.
@solana/pay
kütüphanesi, sağlanan createQR
yardımcı işlevi ile bunu basitleştirir. Bu işlev, şu bilgileri sağlamanızı gerektirir:
url
- işlem talebinin URL'si.boyut
(isteğe bağlı) - QR kodunun genişliği ve yüksekliği piksel olarak. Varsayılan 512'dir.arka plan
(isteğe bağlı) - arka plan rengi. Varsayılan beyazdır.renk
(isteğe bağlı) - ön plan rengi. Varsayılan siyahtır.
const qr = createQR(url, 400, "transparent");
Laboratuvar
Artık Solana Pay hakkında kavramsal bir anlayışa sahip olduğunuza göre, onu pratiğe dökelim. Solana Pay'i bir grup avı için bir dizi QR kodu oluşturmak üzere kullanacağız. Katılımcılar, her bir grup avı konumuna sırayla gitmelidir. Her konumda, sağlanan QR kodunu kullanarak grup avının akıllı sözleşmesine uygun işlemi göndereceklerdir.
1. Başlangıç
Başlamak için, bu depo içinde starter
dalında başlangıç kodunu indirin. Başlangıç kodu, Solana Pay QR kodunu görüntüleyen bir Next.js uygulamasıdır. Menü çubuğunun farklı QR kodları arasında geçiş yapmanıza olanak tanıdığını göreceksiniz. Varsayılan seçenek, gösterim amaçlı basit bir SOL transferidir. Bu laboratuvar boyunca, menü çubuğundaki konum seçeneklerine işlevsellik ekleyeceğiz.
Bunu yapmak için, Devnet'te bir Anchor programını çağırmak için bir işlem oluşturacak yeni bir uç noktasını oluşturacağız. Bu program, bu "grup avı" uygulaması özel olarak tasarlanmış olup iki talimata sahiptir: initialize
ve check_in
. initialize
talimatı, kullanıcının durumunu kurmak için kullanılırken, check_in
talimatı grup avındaki bir konumda check-in kaydetmek için kullanılır.
Bu laboratuvar boyunca programda herhangi bir değişiklik yapmayacağız, ancak program hakkında daha aşina olmak isterseniz, kaynak kodunu kontrol edebilirsiniz.
Devam etmeden önce, grup avı uygulamasının başlangıç kodunu tanıdığınızdan emin olun. pages/index.tsx
, utils/createQrCode/simpleTransfer
, ve /utils/checkTransaction
'a bakmak, SOL göndermek için işlem talebinin nasıl ayarlandığını görmenizi sağlayacaktır. İşlem talebi ile konumda check-in yapma işlemi için benzer bir model izleyeceğiz.
2. Kurulum
İlerlemeye başlamadan önce, uygulamayı yerel olarak çalıştırabildiğinizden emin olalım. Öncelikle, ön uç dizinindeki .env.example
dosyasını .env
olarak yeniden adlandırın. Bu dosya, bu laboratuvar boyunca işlemleri kısmi imzalamak için kullanılacak bir anahtar çiftini içerir.
Sonrasında, yarn
ile bağımlılıkları kurun, ardından yarn dev
kullanarak tarayıcınızı açın localhost:3000
(veya 3000 zaten kullanılıyorsa konsolda belirtilen port).
Artık QR kodunu sayfada mobil cihazınızdan taramayı denerken bir hata alacaksınız. Bunun nedeni, QR kodunun bilgisayarınızdaki localhost:3000
adresine yönlendirilmiş olmasıdır; bu, telefonunuzun erişebileceği bir adres değildir. Daha da önemlisi, Solana Pay'in çalışması için HTTPS URL'si kullanması gerekir.
Bunu aşmak için ngrok kullanabilirsiniz. Daha önce kullanmadıysanız, yüklemeniz gerekecek. Yükledikten sonra, terminalde aşağıdaki komutu çalıştırın, 3000
'i bu proje için kullandığınız herhangi bir portla değiştirin:
ngrok http 3000
Bu, yerel sunucunuza uzaktan erişmek için kullanabileceğiniz benzersiz bir URL sağlayacaktır. Çıktı şöyle bir şey olacaktır:
Session Status online
Account your_email@gmail.com (Plan: Free)
Update update available (version 3.1.0, Ctrl-U to update)
Version 3.0.6
Region United States (us)
Latency 45ms
Web Interface http://127.0.0.1:4040
Forwarding https://7761-24-28-107-82.ngrok.io -> http://localhost:3000
Artık, konsolunuzda gösterilen HTTPS ngrok URL'sini tarayıcınızda açın (örneğin, https://7761-24-28-107-82.ngrok.io). Bu, yerel olarak test ederken mobil cihazınızdan QR kodlarını taramanızı sağlayacaktır.
Yazım anında, bu laboratuvar Solflare ile en iyi şekilde çalışmaktadır. Bazı cüzdanlar, bir Solana Pay QR kodunu tararken yanlış bir uyarı mesajı gösterebilir. Hangi cüzdanı kullanırsanız kullanın, cüzdanda devnet seçtiğinizden emin olun. Ardından, "SOL Transfer" etiketli ana sayfadaki QR kodunu tarayın. Bu QR kodu, basit bir SOL transferi gerçekleştiren bir işlem talebi için bir referans uygulamasıdır. Ayrıca, çoğu insanın test için Devnet SOL'u bulunmadığından mobil cüzdanınızı Devnet SOL ile finanse etmek için requestAirdrop
fonksiyonunu çağırır.
Eğer QR kodu kullanarak işlemi başarıyla gerçekleştirebildiyseniz, devam edebilirsiniz!
3. Bir check-in işlem talep uç noktası oluşturun
Artık çalışmaya başladığınıza göre, Scavenger Hunt programını kullanarak konum check-in’ini destekleyen bir uç nokta oluşturma zamanı geldi.
Aşağıdaki adımları izleyerek etkin bir uç nokta oluşturabilirsiniz.
pages/api/checkIn.ts
dosyasını açarak başlayın. Gizli anahtar ortam değişkeninden eventOrganizer
'ı başlatmak için bir yardımcı işlevi bulabilirsiniz. Bu dosyadaki ilk yapacağımız şey şudur:
- Rastgele bir HTTP isteğini işlemek için bir
handler
işlevi dışa aktarmak - Bu HTTP yöntemlerini işlemek için
get
vepost
fonksiyonları eklemek handler
işlevinin gövdesine, HTTP isteği yöntemine göreget
,post
çağıracak veya 405 hatası döndürecek mantık eklemek
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
request: NextApiRequest,
response: NextApiResponse,
) {
if (request.method === "GET") {
return get(response);
}
if (request.method === "POST") {
return await post(request, response);
}
return response.status(405).json({ error: "Method not allowed" });
}
function get(response: NextApiResponse) {}
async function post(request: NextApiRequest, response: NextApiResponse) {}
4. get
fonksiyonunu güncelleyin
Unutmayın, bir cüzdanın ilk isteği, uç noktanın bir etiket ve simge döndürmesini bekleyen bir GET isteği olacaktır. get
fonksiyonunu "Scavenger Hunt!" etiketi ve bir Solana logosu simgesi ile bir yanıt gönderecek şekilde güncelleyin.
function get(response: NextApiResponse) {
response.status(200).json({
label: "Scavenger Hunt!",
icon: "https://solana.com/src/img/branding/solanaLogoMark.svg",
});
}
5. post
fonksiyonunu güncelleyin
GET isteğinden sonra, bir cüzdan uç noktaya bir POST isteği gönderir. İsteğin body
'sinde son kullanıcıyı temsil eden bir account
alanına sahip bir JSON nesnesi bulunacaktır.
Ayrıca, sorgu parametreleri, QR koduna kodladığınız her şeyi içerecektir. utils/createQrCode/checkIn.ts
dosyasına göz atarsanız, bu uygulamanın reference
ve id
için parametreler içerdiğini göreceksiniz. Bunlar:
reference
- işlemi tanımlamak için kullanılan rastgele üretilmiş bir genel anahtarid
- bir tamsayı olarak konum id'si
post
fonksiyonunu, istekten account
, reference
ve id
çıkarmak için güncelleyin. Bu alanlardan herhangi biri eksikse bir hata ile yanıt vermelisiniz.
Sonra, try catch
ifadesi ekleyin; catch
bloğu bir hatayla yanıt versin ve try
bloğu yeni bir buildTransaction
fonksiyonuna çağrı yapsın. buildTransaction
başarılı olursa, 200 ile yanıt verin ve işlem ile kullanıcının belirtilen konumu bulduğuna dair bir mesaj içeren bir JSON nesnesi ile yanıt verin. Şu anda buildTransaction
fonksiyonunun mantığını düşünmeyin - bunu bir sonraki aşamada yapacağız.
Burada ayrıca PublicKey
ve Transaction
'ı @solana/web3.js
'den içe aktarmanız gerekecek.
import { NextApiRequest, NextApiResponse } from "next"
import { PublicKey, Transaction } from "@solana/web3.js"
...
async function post(request: NextApiRequest, response: NextApiResponse) {
const { account } = request.body;
const { reference, id } = request.query;
if (!account || !reference || !id) {
response.status(400).json({ error: "Missing required parameter(s)" });
return;
}
try {
const transaction = await buildTransaction(
new PublicKey(account),
new PublicKey(reference),
id.toString(),
);
response.status(200).json({
transaction: transaction,
message: `You've found location ${id}!`,
});
} catch (error) {
console.log(error);
response.status(500).json({ transaction: "", message: error.message });
return;
}
}
async function buildTransaction(
account: PublicKey,
reference: PublicKey,
id: string
): Promise<string> {
return new Transaction()
}
6. buildTransaction
fonksiyonunu implement edin
Sonraki adımda, buildTransaction
fonksiyonunu uygulayalım. Bu fonksiyon, check-in işlemini oluşturmalı, kısmi imzalamalı ve geri döndürmelidir. Gerçekleştirmesi gereken işlemler şunlardır:
- Kullanıcı durumunu al
locationAtIndex
yardımcı fonksiyonunu ve konum id'sini kullanarak bir Konum nesnesi al- Kullanıcının doğru konumda olup olmadığını doğrula
- Bağlantıdan mevcut blokhash ve son geçerli blok yüksekliğini al
- Yeni bir işlem nesnesi oluştur
- Kullanıcı durumu mevcut değilse işleme başlatma talimatı ekle
- İşleme check-in talimatı ekle
- Check-in talimatına
reference
genel anahtarını ekle - İşlemi etkinlik organizatörünün anahtarı ile kısmi imzala
- İşlemi base64 kodlaması ile seri hale getir ve işlemi geri döndür
Bu adımların her biri basit olsa da çok sayıda adım var. Fonksiyonu basitleştirmek için, boş yardımcı fonksiyonlar oluşturacağız ve bunları daha sonra adımlar 1, 3, 6, ve 7-8 için dolduracağız. Bunları sırasıyla fetchUserState
, verifyCorrectLocation
, createInitUserInstruction
ve createCheckInInstruction
olarak adlandıracağız.
Aşağıdaki ithalatları da ekleyeceğiz:
import { NextApiRequest, NextApiResponse } from "next";
import {
PublicKey,
Transaction,
TransactionInstruction,
} from "@solana/web3.js";
import { locationAtIndex, Location, locations } from "../../utils/locations";
import { connection, gameId, program } from "../../utils/programSetup";
Boş yardımcı fonksiyonları ve yeni ithalatları kullanarak buildTransaction
fonksiyonunu dolduralım:
async function buildTransaction(
account: PublicKey,
reference: PublicKey,
id: string,
): Promise<string> {
const userState = await fetchUserState(account);
const currentLocation = locationAtIndex(new Number(id).valueOf());
if (!currentLocation) {
throw { message: "Invalid location id" };
}
if (!verifyCorrectLocation(userState, currentLocation)) {
throw { message: "You must visit each location in order!" };
}
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
const transaction = new Transaction({
feePayer: account,
blockhash,
lastValidBlockHeight,
});
if (!userState) {
transaction.add(await createInitUserInstruction(account));
}
transaction.add(
await createCheckInInstruction(account, reference, currentLocation),
);
transaction.partialSign(eventOrganizer);
const serializedTransaction = transaction.serialize({
requireAllSignatures: false,
});
const base64 = serializedTransaction.toString("base64");
return base64;
}
interface UserState {
user: PublicKey;
gameId: PublicKey;
lastLocation: PublicKey;
}
async function fetchUserState(account: PublicKey): Promise<UserState | null> {
return null;
}
function verifyCorrectLocation(
userState: UserState | null,
currentLocation: Location,
): boolean {
return false;
}
async function createInitUserInstruction(
account: PublicKey,
): Promise<TransactionInstruction> {
throw "";
}
async function createCheckInInstruction(
account: PublicKey,
reference: PublicKey,
location: Location,
): Promise<TransactionInstruction> {
throw "";
}
7. fetchUserState
fonksiyonunu implement edin
buildTransaction
fonksiyonu tamamlandığında, oluşturduğumuz boş yardımcı fonksiyonları uygulamaya başlayabiliriz; önce fetchUserState
ile başlayalım. Bu fonksiyon, gameId
ve kullanıcının account
'ından kullanıcı durumu PDA'sını türetir ve ardından bu hesabı alıp varsa onu döndürür.
async function fetchUserState(account: PublicKey): Promise<UserState | null> {
const userStatePDA = PublicKey.findProgramAddressSync(
[gameId.toBuffer(), account.toBuffer()],
program.programId,
)[0];
try {
return await program.account.userState.fetch(userStatePDA);
} catch {
return null;
}
}
8. verifyCorrectLocation
fonksiyonunu implement edin
Sonra, verifyCorrectLocation
yardımcı fonksiyonunu uygulayalım. Bu fonksiyon, bir kullanıcının bir scavenger hunt oyununda doğru konumda olup olmadığını doğrulamak için kullanılır.
Eğer userState
null
ise, bu, kullanıcının ilk lokasyonu ziyaret etmesi gerektiği anlamına gelir. Aksi takdirde, kullanıcı en son ziyaret ettikleri konumlarının indeksine 1 eklenmiş konumu ziyaret etmelidir.
Eğer bu koşullar sağlanıyorsa, fonksiyon true döndürür. Aksi takdirde, false döner.
function verifyCorrectLocation(
userState: UserState | null,
currentLocation: Location,
): boolean {
if (!userState) {
return currentLocation.index === 1;
}
const lastLocation = locations.find(
location => location.key.toString() === userState.lastLocation.toString(),
);
if (!lastLocation || currentLocation.index !== lastLocation.index + 1) {
return false;
}
return true;
}
9. Talimat oluşturma fonksiyonlarını implement edin
Son olarak, createInitUserInstruction
ve createCheckInInstruction
fonksiyonlarını uygulayalım. Bu fonksiyonlar, karşılık gelen talimatları oluşturmak ve döndürmek için Anchor kullanabilir. Tek ihtiyacımız olan, createCheckInInstruction
'ın anahtarlar listesine reference
'ı eklemesidir.
async function createInitUserInstruction(
account: PublicKey,
): Promise<TransactionInstruction> {
const initializeInstruction = await program.methods
.initialize(gameId)
.accounts({ user: account })
.instruction();
return initializeInstruction;
}
async function createCheckInInstruction(
account: PublicKey,
reference: PublicKey,
location: Location,
): Promise<TransactionInstruction> {
const checkInInstruction = await program.methods
.checkIn(gameId, location.key)
.accounts({
user: account,
eventOrganizer: eventOrganizer.publicKey,
})
.instruction();
checkInInstruction.keys.push({
pubkey: reference,
isSigner: false,
isWritable: false,
});
return checkInInstruction;
}
10. Uygulamayı test edin
Bu aşamada uygulamanız çalışıyor olmalı! Mobil cüzdanınızı kullanarak test edin. Location 1
için QR kodunu tarayarak başlayın. Öncelikle, ön yüzünüzün localhost
yerine ngrok URL'sini kullandığından emin olun.
QR kodunu taradıktan sonra, konum 1'de olduğunuzu belirten bir mesaj görmelisiniz. Ardından, Location 2
sayfasındaki QR kodunu tarayın. Önceki işlemin nihai hale gelmesi için birkaç saniye beklemeniz gerekebilir.
Tebrikler, Solana Pay kullanarak scavenger hunt demo'sunu başarılı bir şekilde tamamladınız! Geçmişiniz nedeniyle, bu başlangıçta sezgisel veya basit olmayabilir. Eğer öyleyse, mutlaka laboratuvarı tekrar gözden geçirin veya kendi projenizi oluşturun. Solana Pay, gerçek yaşam ve zincir üstü etkileşim arasında köprü kuran pek çok kapıyı açıyor.
Son çözüm koduna göz atmak isterseniz, bunu aynı depoda bulabilirsiniz.
Görev
Artık bunu kendi başınıza deneme zamanı. Solana Pay kullanarak kendi fikirlerinizi geliştirmekten çekinmeyin. Ya da, biraz ilham almak istiyorsanız, aşağıdaki öneriyi kullanabilirsiniz.
Solana Pay kullanarak (ya da laboratuvardakini değiştirerek) kullanıcılara NFT mint etmek için bir uygulama oluşturun. Bir adım daha ileri giderek, işlemin yalnızca kullanıcı bir veya daha fazla koşulu yerine getirirse (örn. belirli bir koleksiyondan bir NFT tutuyorsa, önceden belirlenmiş bir listede yer alıyorsa vb.) gerçekleştirilmesini sağlayın.
Bununla yaratıcı olun! Solana Pay spesifikasyonu, birçok benzersiz kullanım durumu için pek çok kapı açar.
Kodunuzu GitHub'a yükleyin ve bize bu ders hakkında ne düşündüğünüzü söyleyin!